<?php

class Image
{
	const DO_SCALE = 1; //uniform image scaling
	const DO_CROP = 2; //crop image (if scaling, then scaled first)
	const ALLOW_UPSCALE = 4; //scale even if original smaller than target
	const FIT = 8; //means background+resized image on it
	const DIRECT = 16; //just simple output


	static $defaults = array(
		'format' => 'png', //default is png cos has alpha
		'quality' => 90,
		'bg' => "#ffffff00", //RGBA format, default is fully transparent white
		'do' => Image::DO_SCALE
	);

	/**
	 * Load image types config
	 * Returns requested type config
	 * 
	 * @throws Exception_DataNotExists
	 * @return array
	 */
	public static function getType($type) {
		static $imagesTypes = array();
		include_once 'imagesTypes.php';
		if (!isset($imagesTypes[$type]))
			throw new Exception_DataNotExists();
		
		return $imagesTypes[$type];
	}
	/**
	 * Crops image
	 *
	 * @param $data image binary
	 * @param $x1
	 * @param $y1
	 * @param $x2
	 * @param $y2
	 * @return cropped image binary in png format for save alpha and lossles
	 */
	public static function &crop (&$data, $x1, $y1, $x2, $y2)
	{
		$im = @imagecreatefromstring($data);
		if (!$im)
			throw new Exception_IncorrectValueType('Can not handle image');

		imagealphablending($im, true);
		imagesavealpha($im, true);

		$w = imagesx($im);
		$h = imagesy($im);

		$nW = $x2 - $x1;
		$nH = $y2 - $y1;

		$bg = imagecreatetruecolor($nW, $nH);
		imagealphablending($bg, true);
		imagesavealpha($bg, true);

		$color = imagecolorallocatealpha($bg, 255, 255, 255, 127);
		imagefill($bg, 0, 0, $color);

		imagecopyresampled($bg, $im, 0, 0, $x1, $y1, $nW, $nH, $nW, $nH);
		imagedestroy($im);

		ob_start();
		imagepng($bg);
		imagedestroy($bg);
		$ret = ob_get_clean();
		return $ret;
	}

	/**
	 * Returns resized image data.
	 *
	 */
	public static function &resize (&$data, $type, array $params = array())
	{
		$params = array_merge(self::getType($type), $params);
		
		return self::_resize($data, $params);
	}

	public static function getSize (&$data)
	{
		$im = @imagecreatefromstring($data);
		if (!$im)
			throw new Exception_IncorrectValueType('Can not handle image');

		$w = imagesx($im);
		$h = imagesy($im);

		imagedestroy($im);

		return compact('w', 'h');
	}

	private static function &_resize (&$data, $params)
	{
		$ret = null;
		if ($data === null)
		{
			$ret = array('mime' => 'text/plain', 'data' => &$data);
			return $ret;
		}

		$im = @imagecreatefromstring($data);
		if (!$im)
			throw new Exception_IncorrectValueType('Can not handle image');

		imagealphablending($im, true);
		imagesavealpha($im, true);

		if (isset($params['imagesize']))
		{
			$w = (int)$params['imagesize']['w'];
			$h = (int)$params['imagesize']['h'];
			$mime = $params['imagesize']['mime'];
		}
		else
		{
			$w = imagesx($im);
			$h = imagesy($im);
			$mime = self::_getImgType($data);
		}

		$_w = (int)$params['w'];
		$_h = (int)$params['h'];

		$action = @$params['do'] ?  : Image::DO_SCALE;
		$format = @$params['format'] ?  : 'png';

		if (($action & Image::DIRECT) || //action is not set
			(!($action & Image::DO_SCALE) && !($action & Image::DO_CROP)) || //original image less or equal than requested
			(!($action & Image::ALLOW_UPSCALE) && !($action & Image::FIT) && $_h >= $h && $_w >= $w))
		{
			//just output
			if ($format == $mime)
			{
				$ret = array('mime' => "image/$format", 'data' => &$data);
				return $ret;
			}
			//or convert
			else
				$action = Image::DIRECT;
		}

		$scale = $action & Image::DO_SCALE;
		$crop = $action & Image::DO_CROP;
		$useBg = $action & Image::FIT;
		$nW = $w;
		$nH = $h;

		//scale
		if ($scale)
		{
			//fit from outside
			if ($crop)
			{
				$coef = max($_h / $h, $_w / $w);
				$nH = (int) ($h * $coef);
				$nW = (int) ($w * $coef);
			}
			//fit from inside
			else
			{
				$coef = min($_h / $h, $_w / $w);
				$nH = (int) ($h * $coef);
				$nW = (int) ($w * $coef);
			}

			if (!($action & Image::ALLOW_UPSCALE))
			{
				if ($nH > $h || $nW > $w)
				{
					$nW = $w;
					$nH = $h;
					//no crop when upscaled
					$crop = false;
					//use bg instead
					$useBg = true;
				}
			}
		}

		if ($useBg || $crop)
		{
			$bg = imagecreatetruecolor($_w, $_h);
			imagealphablending($bg, true);
			imagesavealpha($bg, true);

			if ($useBg)
			{
				$rgba = @sscanf(str_ireplace('#', '', $params['bg']), '%2x%2x%2x%2x');
				$color = imagecolorallocatealpha($bg, $rgba[0], $rgba[1], $rgba[2], 127 - $rgba[3]);
				imagefill($bg, 0, 0, $color);
			}
			else
			{
				$color = imagecolorallocatealpha($bg, 0, 0, 0, 127);
				imagefill($bg, 0, 0, $color);
			}

		}

		if ($scale)
		{
			$target = imagecreatetruecolor($nW, $nH);
			imagealphablending($target, true);
			imagesavealpha($target, true);

			$color = imagecolorallocatealpha($target, 0, 0, 0, 127);
			imagefill($target, 0, 0, $color);

			imagecopyresampled($target, $im, 0, 0, 0, 0, $nW, $nH, $w, $h);
			imagedestroy($im);
		}
		else
			$target = $im;

		if (isset($bg))
		{
			imagecopy($bg, $target, (int) (($_w - $nW) / 2), (int) (($_h - $nH) / 2), 0, 0, $nW, $nH);
			$out = $bg;
			imagedestroy($target);
		}
		else
			$out = $target;

		$func = "image" . $format;

		ob_start();
		if (isset($params['quality']) && $params['quality'] >= 0 && (($format == "jpeg" && $params['quality'] <= 100) || ($format == "png" && $params['quality'] <= 9)))
		{
			$func($out, null, $params['quality']);
		}
		else
		{
			$func($out);
		}
		$dataNew = ob_get_clean();

		$ret = array('mime' => "image/$format", 'data' => &$dataNew);
		return $ret;
	}

	private static function _getImgType (&$data)
	{
		$jpeg = implode(array(0xFF, 0xD8));
		$png = implode(array(0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A));
		$gif = implode(array(0x47, 0x49, 0x46));

		if (strncmp($data, $jpeg, 2))
			return 'jpeg';
		if (strncmp($data, $png, 8))
			return 'png';
		if (strncmp($data, $gif, 3))
			return 'gif';

		return 'unknown';
	}
}

class Exception_IncorrectValueType extends Exception
{

}

class Exception_DataNotExists extends Exception
{

}